/*
 * Copyright 2010 Francisco Modesto
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.googlecode.gwt.tinyaop.rebind;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

import com.google.gwt.core.ext.typeinfo.JMethod;
import com.google.gwt.core.ext.typeinfo.JParameter;
import com.google.gwt.core.ext.typeinfo.JType;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.user.rebind.SourceWriter;

public class FieldBuilder implements Builder {
  private JMethod method;
  private int index;
  private Class<? extends Annotation>[] annotations;

  public FieldBuilder(JMethod method, int index, Class<? extends Annotation>[] annotations) {
    this.method = method;
    this.index = index;
    this.annotations = annotations;
  }

  @Override
  public void create(SourceWriter writer) {
    writer.println("private static final MethodDescriptor _METHOD_" + index
        + " = new MethodDescriptor() {");
    writer.indent();
    writeAnnotationsFields(writer);
    writer.println();
    writer.println("@Override");
    writer.println("public Class<?> getDeclaringType() {");
    writer.indentln("return " + declaringType() + ";");
    writer.println("}");
    writer.println();
    writer.println("@Override");
    writer.println("public String getName() {");
    writer.indentln("return " + name() + ";");
    writer.println("}");
    writer.println();
    writer.println("@Override");
    writer.println("public Class<?> getReturnType() {");
    writer.indentln("return " + returnType() + ";");
    writer.println("}");
    writer.println();
    writer.println("@Override");
    writer.println("public Class<?>[] getParametersType() {");
    writer.indentln("return " + parameters() + ";");
    writer.println("}");
    writer.println();
    writer.println("@Override");
    writer.println("public <T extends Annotation> T getAnnotation(Class<T> annotation) {");
    writeAnnotations(writer);
    writer.println("}");
    writer.outdent();
    writer.println("};");
  }

  private String declaringType() {
    return method.getEnclosingType().getQualifiedSourceName() + ".class";
  }

  private String name() {
    return '"' + method.getName() + '"';
  }

  private String returnType() {
    return erasure(method.getReturnType()) + ".class";
  }

  private String parameters() {
    JParameter[] params = method.getParameters();
    StringBuilder sb = new StringBuilder();
    sb.append("new Class[] {");
    for (int i = 0; i < params.length; i++) {
      if (i != 0) {
        sb.append(", ");
      }
      sb.append(erasure(params[i].getType())).append(".class");
    }
    sb.append("}");
    return sb.toString();
  }

  private String erasure(JType type) {
    return type.getErasedType().getQualifiedSourceName();
  }

  private void writeAnnotationsFields(SourceWriter writer) {
    int index = 0;
    for (Class<? extends Annotation> annotation : annotations) {
      if (method.isAnnotationPresent(annotation)) {
        String type = annotation.getCanonicalName();
        writer.println("final " + type + " _annotation_" + index++ + " = new " + type + "() {");
        writer.indent();
        writer.println("@Override");
        writer.println("public Class<? extends Annotation> annotationType() {");
        writer.indentln("return " + type + ".class;");
        writer.println("}");
        writeAnnotationsMethods(writer, annotation);
        writer.outdent();
        writer.println("};");
      }
    }
  }

  private void writeAnnotationsMethods(SourceWriter writer, Class<? extends Annotation> annotation) {
    TypeOracle oracle = method.getEnclosingType().getOracle();
    JType type = oracle.findType(annotation.getCanonicalName());
    if (type == null || type.isAnnotation() == null) {
      throw new UncheckUnableToCompleteException("Custom annotation not found");
    }
    JMethod[] methods = type.isAnnotation().getMethods();
    for (JMethod m : methods) {
      Object value = retrieveValue(annotation, m.getName());
      String retValue = Constants.toJava(value);
      String exp;
      if (m.getReturnType().isArray() != null) {
        String retType = m.getReturnType().getQualifiedSourceName();
        exp = "return new " + retType + " " + retValue + ";";
      }
      else {
        exp = "return " + retValue + ";";
      }
      new MethodBuilder(m, new RawBuilder(exp), false).create(writer);
    }
  }

  private Object retrieveValue(Class<? extends Annotation> annotation, String annotationMethod) {
    try {
      Method realMethod = annotation.getMethod(annotationMethod);
      return realMethod.invoke(method.getAnnotation(annotation));
    }
    catch (Exception e) {
      return null;
    }
  }

  private void writeAnnotations(SourceWriter writer) {
    writer.indent();
    int index = 0;
    for (Class<? extends Annotation> annotation : annotations) {
      if (method.isAnnotationPresent(annotation)) {
        writer.println("if (annotation == " + annotation.getCanonicalName() + ".class) {");
        writer.indentln("return (T) _annotation_" + index++ + ";");
        writer.println("}");
      }
    }
    writer.println("return null;");
    writer.outdent();
  }
}
